package com.chenfan.magic.core;

import com.chenfan.magic.constant.SecureConstant;
import com.chenfan.magic.constant.TokenConstant;
import com.chenfan.magic.entity.IdcUser;
import com.chenfan.magic.entity.TokenInfo;
import com.chenfan.magic.exception.SecureException;
import com.chenfan.magic.service.IClientDetails;
import com.chenfan.magic.service.IClientDetailsService;
import com.chenfan.magic.utils.*;
import com.google.common.base.Charsets;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.SneakyThrows;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.*;

/**
 * Secure工具类
 *
 * @author troy
 */
public class SecureUtil {
    private static final String IDC_USER_REQUEST_ATTR = "_IDC_USER_REQUEST_ATTR_";

    private final static String HEADER = TokenConstant.HEADER;
    private final static String BEARER = TokenConstant.BEARER;
    private final static String USERNAME = TokenConstant.USERNAME;
    private final static String USER_ID = TokenConstant.USER_ID;
    private final static String ROLE_ID = TokenConstant.ROLE_ID;
    private final static String NICKNAME = TokenConstant.NICKNAME;
    private final static String ROLE_NAME = TokenConstant.ROLE_NAME;
    private final static String TENANT_ID = TokenConstant.TENANT_ID;
    private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
    private final static Integer AUTH_LENGTH = TokenConstant.AUTH_LENGTH;
    private static String BASE64_SECURITY = Base64.getEncoder().encodeToString(TokenConstant.SIGN_KEY.getBytes(Charsets.UTF_8));

    private static IClientDetailsService clientDetailsService;

//    static {
//        clientDetailsService = SpringUtil.getBean(IClientDetailsService.class);
//    }

    /**
     * 获取用户信息
     *
     * @return IdcUser
     */
    public static IdcUser getUser() {
        HttpServletRequest request = WebKit.getRequest();
        if (request == null) {
            return null;
        }
        // 优先从 request 中获取
        Object idcUser = request.getAttribute(IDC_USER_REQUEST_ATTR);
        if (idcUser == null) {
            idcUser = getUser(request);
            if (idcUser != null) {
                // 设置到 request 中
                request.setAttribute(IDC_USER_REQUEST_ATTR, idcUser);
            }
        }
        return (IdcUser) idcUser;
    }

    /**
     * 获取用户信息
     *
     * @param request request
     * @return IdcUser
     */
    public static IdcUser getUser(HttpServletRequest request) {
        Claims claims = getClaims(request);
        if (claims == null) {
            return null;
        }
        String clientId = Func.toStr(claims.get(SecureUtil.CLIENT_ID));
        Long userId = Func.toLong(claims.get(SecureUtil.USER_ID));
        String tenantId = Func.toStr(claims.get(SecureUtil.TENANT_ID));
        String roleId = Func.toStr(claims.get(SecureUtil.ROLE_ID));
        String username = Func.toStr(claims.get(SecureUtil.USERNAME));
        String roleName = Func.toStr(claims.get(SecureUtil.ROLE_NAME));
        String nickname = Func.toStr(claims.get(SecureUtil.NICKNAME));
        IdcUser idcUser = new IdcUser();
        idcUser.setAppKey(clientId);
        idcUser.setUserId(userId);
        idcUser.setTenantId(tenantId);
        idcUser.setUsername(username);
        idcUser.setRoleId(roleId);
        idcUser.setRoleName(roleName);
        idcUser.setNickname(nickname);
        return idcUser;
    }


    /**
     * 获取用户id
     *
     * @return userId
     */
    public static Long getUserId() {
        IdcUser user = getUser();
        return (null == user) ? -1 : user.getUserId();
    }

    /**
     * 获取用户id
     *
     * @param request request
     * @return userId
     */
    public static Long getUserId(HttpServletRequest request) {
        IdcUser user = getUser(request);
        return (null == user) ? -1 : user.getUserId();
    }

    /**
     * 获取用户账号
     *
     * @return userAccount
     */
    public static String getUsername() {
        IdcUser user = getUser();
        return (null == user) ? StringPool.EMPTY : user.getUsername();
    }

    /**
     * 获取用户账号
     *
     * @param request request
     * @return userAccount
     */
    public static String getUsername(HttpServletRequest request) {
        IdcUser user = getUser(request);
        return (null == user) ? StringPool.EMPTY : user.getUsername();
    }

    /**
     * 获取用户名
     *
     * @return userName
     */
    public static String getNickname() {
        IdcUser user = getUser();
        return (null == user) ? StringPool.EMPTY : user.getNickname();
    }

    /**
     * 获取用户名
     *
     * @param request request
     * @return userName
     */
    public static String getNickname(HttpServletRequest request) {
        IdcUser user = getUser(request);
        return (null == user) ? StringPool.EMPTY : user.getNickname();
    }

    /**
     * 获取用户角色
     *
     * @return userName
     */
    public static String getUserRole() {
        IdcUser user = getUser();
        return (null == user) ? StringPool.EMPTY : user.getRoleName();
    }

    /**
     * 获取用角色
     *
     * @param request request
     * @return userName
     */
    public static String getUserRole(HttpServletRequest request) {
        IdcUser user = getUser(request);
        return (null == user) ? StringPool.EMPTY : user.getRoleName();
    }

    /**
     * 获取租户ID
     *
     * @return tenantId
     */
    public static String getTenantId() {
        IdcUser user = getUser();
        return (null == user) ? StringPool.EMPTY : user.getTenantId();
    }

    /**
     * 获取租户ID
     *
     * @param request request
     * @return tenantId
     */
    public static String getTenantId(HttpServletRequest request) {
        IdcUser user = getUser(request);
        return (null == user) ? StringPool.EMPTY : user.getTenantId();
    }

    /**
     * 获取客户端id
     *
     * @return tenantId
     */
    public static String getAppKey() {
        IdcUser user = getUser();
        return (null == user) ? StringPool.EMPTY : user.getAppKey();
    }

    /**
     * 获取客户端id
     *
     * @param request request
     * @return tenantId
     */
    public static String getAppKey(HttpServletRequest request) {
        IdcUser user = getUser(request);
        return (null == user) ? StringPool.EMPTY : user.getAppKey();
    }

    /**
     * 获取Claims
     *
     * @param request request
     * @return Claims
     */
    public static Claims getClaims(HttpServletRequest request) {
        String auth = request.getHeader(SecureUtil.HEADER);
        if ((auth != null) && (auth.length() > AUTH_LENGTH)) {
            String headStr = auth.substring(0, 6).toLowerCase();
            if (headStr.compareTo(SecureUtil.BEARER) == 0) {
                auth = auth.substring(7);
                return SecureUtil.parseJWT(auth);
            }
        }
        return null;
    }

    /**
     * 获取请求头
     *
     * @return header
     */
    public static String getHeader() {
        return getHeader(Objects.requireNonNull(WebKit.getRequest()));
    }

    /**
     * 获取请求头
     *
     * @param request request
     * @return header
     */
    public static String getHeader(HttpServletRequest request) {
        return request.getHeader(HEADER);
    }

    /**
     * 解析jsonWebToken
     *
     * @param jsonWebToken jsonWebToken
     * @return Claims
     */
    public static Claims parseJWT(String jsonWebToken) {
        try {
            return Jwts.parser()
                    .setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY))
                    .parseClaimsJws(jsonWebToken).getBody();
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 创建令牌
     *
     * @param user      user
     * @param audience  audience
     * @param issuer    issuer
     * @param tokenType tokenType
     * @return jwt
     */
    public static TokenInfo createJWT(Map<String, String> user, String audience, String issuer, String tokenType) {

        String[] tokens = extractAndDecodeHeader();
        assert tokens.length == 2;
        String clientId = tokens[0];
        String clientSecret = tokens[1];

        // 获取客户端信息
        IClientDetails clientDetails = clientDetails(clientId);

        // 校验客户端信息
        if (!validateClient(clientDetails, clientId, clientSecret)) {
            throw new SecureException("应用认证失败!");
        }

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //生成签名密钥
        byte[] apiKeySecretBytes = Base64.getDecoder().decode(BASE64_SECURITY);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        //添加构成JWT的类
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JsonWebToken")
                .setIssuer(issuer)
                .setAudience(audience)
                .signWith(signatureAlgorithm, signingKey);

        //设置JWT参数
        user.forEach(builder::claim);

        //设置应用id
        builder.claim(CLIENT_ID, clientId);

        //添加Token过期时间: 默认为次日凌晨 3 点过期.
        long expireMillis = getExpire();
        if (tokenType.equals(TokenConstant.ACCESS_TOKEN) && clientDetails.getAccessTokenValidity() >= 60) {
            expireMillis = clientDetails.getAccessTokenValidity() * 1000;
        } else if (tokenType.equals(TokenConstant.REFRESH_TOKEN) && clientDetails.getRefreshTokenValidity() >= 60) {
            expireMillis = clientDetails.getRefreshTokenValidity() * 1000;
        }
        long expMillis = nowMillis + expireMillis;
        Date exp = new Date(expMillis);
        builder.setExpiration(exp).setNotBefore(now);

        // 组装Token信息
        TokenInfo tokenInfo = new TokenInfo();
        tokenInfo.setToken(builder.compact());
        tokenInfo.setExpire((int) expireMillis / 1000);
        tokenInfo.setExpireDate(exp);

        return tokenInfo;
    }

    /**
     * 获取过期时间(次日凌晨3点)
     *
     * @return expire
     */
    public static long getExpire() {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DAY_OF_YEAR, 1);
        cal.set(Calendar.HOUR_OF_DAY, 3);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTimeInMillis() - System.currentTimeMillis();
    }

    /**
     * 客户端信息解码
     */
    @SneakyThrows
    public static String[] extractAndDecodeHeader() {
        // 获取请求头客户端信息
        String header = Objects.requireNonNull(WebKit.getRequest()).getHeader(SecureConstant.BASIC_HEADER_KEY);
        header = Func.toStr(header).replace(SecureConstant.BASIC_HEADER_PREFIX_EXT, SecureConstant.BASIC_HEADER_PREFIX);
        if (!header.startsWith(SecureConstant.BASIC_HEADER_PREFIX)) {
            throw new SecureException("No client information in request header");
        }
        byte[] base64Token = header.substring(6).getBytes(Charsets.UTF_8);

        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException var7) {
            throw new RuntimeException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, Charsets.UTF_8);
        int index = token.indexOf(StringPool.COLON);
        if (index == -1) {
            throw new RuntimeException("Invalid basic authentication token");
        } else {
            return new String[]{token.substring(0, index), token.substring(index + 1)};
        }
    }

    /**
     * 获取请求头中的客户端id
     */
    public static String getClientIdFromHeader() {
        String[] tokens = extractAndDecodeHeader();
        assert tokens.length == 2;
        return tokens[0];
    }

    /**
     * 获取客户端信息
     *
     * @param clientId 客户端id
     * @return clientDetails
     */
    private static IClientDetails clientDetails(String clientId) {
        return clientDetailsService.loadClientByClientId(clientId);
    }

    /**
     * 获取真实过期时间
     *
     * @return
     */
    public static Date getRealExpireTime() {
        return clientDetailsService.getRealExpireTime(getAppKey(), getUserId());
    }

    /**
     * 校验Client
     *
     * @param clientId     客户端id
     * @param clientSecret 客户端密钥
     * @return boolean
     */
    private static boolean validateClient(IClientDetails clientDetails, String clientId, String clientSecret) {
        if (clientDetails != null) {
            return StringUtil.equals(clientId, clientDetails.getAppKey()) && StringUtil.equals(clientSecret, clientDetails.getAppSecret());
        }
        return false;
    }

}
